/**
 * 
 */
package com.android.vocalNotes;

import java.io.FileNotFoundException;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.ContentUris;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.os.Environment;
import android.view.ContextMenu;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RatingBar;
import android.widget.SimpleCursorAdapter;
import android.widget.Toast;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.TextView;
import com.android.vocalNotes.VocalNotes;

/**
 * This class is the main activity, that is viewed when the user launch the
 * application from the Launcher.
 * 
 * @author Daniela Cabiddu, Maurizio Kovacic, Giorgio Marcias
 */
public class VocalNotesListActivity extends Activity {

   /**
    * This private member stores the cursor for the vocal notes ordered by date.
    */
   private Cursor dateOrderedCursor;

   /**
    * This private member stores the cursor for the vocal notes ordered by rate.
    */
   private Cursor rateOrderedCursor;

   /**
    * This private member is an adapter for the vocal notes ordered by date.
    */
   SimpleCursorAdapter dateAdapter;

   /**
    * This private member is an adapter for the vocal notes ordered by rating.
    */
   SimpleCursorAdapter rateAdapter;

   /**
    * This private member is the current adapter.
    */
   SimpleCursorAdapter currentAdapter;

   /**
    * This private member stores the number of rating stars (0 - 5) for
    * filtering the vocal notes by which. -1 -> no filter.
    */
   private int ratingStarsFilter;

   /**
    * String key for the shared preferences value of the filter.
    */
   private static final String FILTER_KEY = "filter_key";

   /**
    * Boolean member that stores the state of the ordering menu. The second menu
    * option is set to "ORDER BY RATING". It is set to true when the menu option
    * is set to "ORDER BY DATE".
    */
   private boolean orderingState;

   /**
    * String key for the shared preferences value of the ordering state.
    */
   private static final String ORDERING_KEY = "ordering_key";

   /**
    * Integer constant value to identify the VIEW option in the context menu.
    */
   private static final int VIEW_ITEM = 0;

   /**
    * Integer constant value to identify the EDIT option in the context menu.
    */
   private static final int EDIT_ITEM = 1;

   /**
    * Integer constant value to identify the DELETE option in the context menu.
    */
   private static final int DELETE_ITEM = 2;

   /**
    * Integer constant value to identify the SEND option in the context menu.
    */
   private static final int SEND_ITEM = 3;

   /**
    * This method is responsible of initializing this activity at the creation
    * moment.
    * 
    * @param savedInstanceState
    *           A Bundle of information that describes the last state to reset.
    */
   @Override
   protected void onCreate(Bundle savedInstanceState) {
      // call super onCreate()
      super.onCreate(savedInstanceState);

      // check the external storage state: it must be mounted and writable
      String state = Environment.getExternalStorageState();
      if (!Environment.MEDIA_MOUNTED.equals(state)
            || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
         // if the external storage media is not mounted notify the user and
         // exit

         // create the builder of the alert dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(this);

         // set the message
         builder.setMessage(R.string.external_storage_error);

         // set not cancelable
         builder.setCancelable(false);

         // set listener for positive button
         builder.setPositiveButton(R.string.yes,
               new DialogInterface.OnClickListener() {
                  public void onClick(DialogInterface dialog, int id) {
                     // finish this activity
                     finish();
                  }
               });

         // create the alert dialog from the builder
         AlertDialog alert = builder.create();

         // show the alert dialog
         alert.show();

      }

      // get the preferences
      SharedPreferences preferences = getPreferences(Context.MODE_PRIVATE);

      // get the last saved ordering state (default: true <-> "ORDER BY DATE")
      orderingState = preferences.getBoolean(ORDERING_KEY, true);

      // get the last saved rating filter (default: -1)
      ratingStarsFilter = preferences.getInt(FILTER_KEY, -1);

      // get the cursor for the vocal notes ordered by date
      dateOrderedCursor = managedQuery(VocalNotes.CONTENT_URI_NOTES,
            VocalNotes.VocalNotesContentProviderAPI.PROJECTION, null, null,
            null);

      // construct the selection
      String selection = null;
      if (ratingStarsFilter > -1) {
         selection = VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING
               + " = " + ratingStarsFilter;

         // show a toast to say that the list is filtered by rating
         Toast.makeText(this, R.string.filtered_list, Toast.LENGTH_SHORT)
               .show();
      }

      // get the cursor for the vocal notes ordered by rate
      rateOrderedCursor = managedQuery(VocalNotes.CONTENT_URI_NOTES,
            VocalNotes.VocalNotesContentProviderAPI.PROJECTION, selection,
            null, VocalNotes.VocalNotesContentProviderAPI.ORDER_BY_RATING);

      // set the adapter for date ordered vocal notes
      dateAdapter = new SimpleCursorAdapter(this, R.layout.main_list_item,
            dateOrderedCursor,
            VocalNotes.VocalNotesContentProviderAPI.SIMPLE_PROJECTION,
            new int[] { R.id.note_title, R.id.date_format, R.id.note_rating });

      // set the adapter for rating ordered vocal notes
      rateAdapter = new SimpleCursorAdapter(this, R.layout.main_list_item,
            rateOrderedCursor,
            VocalNotes.VocalNotesContentProviderAPI.SIMPLE_PROJECTION,
            new int[] { R.id.note_title, R.id.date_format, R.id.note_rating });

      // construct a binder for rating and date
      ListBinder binder = new ListBinder();
      // set the binder to the adapters
      dateAdapter.setViewBinder(binder);
      rateAdapter.setViewBinder(binder);

      // set the current adapter
      if (orderingState) {
         currentAdapter = dateAdapter;
      } else {
         currentAdapter = rateAdapter;
      }

      // set the layout
      setContentView(R.layout.main_list_view);

      // get the list used to show notes in the database
      ListView notes = (ListView) findViewById(R.id.notelist);

      // set the current adapter
      notes.setAdapter(currentAdapter);

      // register the context menu for each item in the list view
      registerForContextMenu(notes);

      // set the listener for actions to take when user clicks on a single item
      // in the list
      notes.setOnItemClickListener(new OnItemClickListener() {
         @Override
         public void onItemClick(AdapterView<?> adapter, View view,
               int position, long id) {
            // construct the intent
            Intent i = new Intent(view.getContext(),
                  VocalNotesItemActivity.class);

            // set the action view
            i.setAction(android.content.Intent.ACTION_VIEW);

            // obtain the content: URI
            Uri uri = ContentUris.withAppendedId(VocalNotes.CONTENT_URI_NOTES,
                  id);

            // set the content: URI and its MIME type
            i.setDataAndType(uri, VocalNotes.NOTES_CONTENT_TYPE_ITEM);

            // start the activity
            startActivity(i);
         }
      });
   }

   /**
    * This method is responsible of take updated the state of the UI when this
    * activity windows is resumed and take focus.
    */
   @Override
   protected void onResume() {
      // call super onResume()
      super.onResume();

      // get the list used to show notes in the database
      ListView notes = (ListView) findViewById(R.id.notelist);

      // set the current adapter
      notes.setAdapter(currentAdapter);

      // check if the database is empty
      if (!dateOrderedCursor.moveToFirst()) {
         // if it is, then show an alert dialog

         // create an alert
         AlertDialog.Builder builder = new AlertDialog.Builder(this);

         // set the message
         builder.setMessage(R.string.empty_db);

         // set positive button on click action
         builder.setPositiveButton(R.string.yes,
               new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int id) {
                     // create a new intent
                     Intent i = new Intent(getApplication()
                           .getApplicationContext(),
                           VocalNotesItemActivity.class);

                     // set the action view
                     i.setAction(android.content.Intent.ACTION_INSERT);

                     // set data and type
                     i.setDataAndType(VocalNotes.CONTENT_URI_NOTES,
                           VocalNotes.NOTES_CONTENT_TYPE);

                     // start the activity
                     startActivity(i);
                  }
               });

         // set negative button on click action
         builder.setNegativeButton(R.string.no,
               new DialogInterface.OnClickListener() {
                  @Override
                  public void onClick(DialogInterface dialog, int id) {
                     // close the alert
                     dialog.cancel();
                  }
               });

         // create the alert dialog from the builder
         AlertDialog alert = builder.create();

         // show the alert dialog
         alert.show();
      }
   }

   /**
    * This method is responsible of saving the state of this activity just
    * before finishing. In particular, it saves into preferences the last
    * ordering state and the rating filter, so that, when the application is
    * reopened, the user sees the identical UI of the last session.
    */
   @Override
   protected void onDestroy() {
      super.onDestroy();

      // save the preferences:
      // get the editor from the unique preferences file
      SharedPreferences.Editor prefEditor = getPreferences(Context.MODE_PRIVATE)
            .edit();

      // save the last ordering state: true <-> "ORDER BY DATE" , false <->
      // "ORDER BY RATING"
      prefEditor.putBoolean(ORDERING_KEY, orderingState);

      // save the last filter
      prefEditor.putInt(FILTER_KEY, ratingStarsFilter);

      // commit the edits
      prefEditor.commit();
   }

   /**
    * This method is responsible of creating the context menu for a single item
    * in the list of vocal notes.
    * 
    * @param menu
    *           The contextual menu.
    * @param v
    *           The view the context menu is binded to.
    * @param menuInfo
    *           Additional information on the view.
    */
   @Override
   public void onCreateContextMenu(ContextMenu menu, View v,
         ContextMenu.ContextMenuInfo menuInfo) {
      super.onCreateContextMenu(menu, v, menuInfo);

      // get additional information about the selected note
      AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;

      // get the selected view (the row of the list view)
      LinearLayout item = (LinearLayout) info.targetView;

      // get the title of the selected note
      ScrollingTextView title = (ScrollingTextView) item
            .findViewById(R.id.note_title);

      // set the title of the note as the header of the context menu
      menu.setHeaderTitle(title.getText());

      // add the four context menu options: view, edit, delete and send the
      // selected note
      menu.add(ContextMenu.NONE, VIEW_ITEM, ContextMenu.NONE,
            R.string.contextual_view);
      menu.add(ContextMenu.NONE, EDIT_ITEM, ContextMenu.NONE,
            R.string.contextual_edit);
      menu.add(ContextMenu.NONE, DELETE_ITEM, ContextMenu.NONE,
            R.string.contextual_delete);
      menu.add(ContextMenu.NONE, SEND_ITEM, ContextMenu.NONE,
            R.string.contextual_send);
   }

   /**
    * This method is responsible of executing the proper action to the context
    * menu item selected by the user. It is automatically called whenever an
    * item in a context menu is selected.
    * 
    * @param item
    *           The context menu item that was selected.
    * @return false to allow normal context menu processing to proceed, true to
    *         consume it here.
    */
   @Override
   public boolean onContextItemSelected(MenuItem item) {
      // the intent used to start the proper activity
      Intent i;

      // get information about the selected note
      AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();

      // get the id of the selected note
      long id = info.id;

      // obtain its content: URI
      Uri uri = ContentUris.withAppendedId(VocalNotes.CONTENT_URI_NOTES, id);

      // switch on the context menu item
      switch (item.getItemId()) {
      // case of viewing the selected note
      case VIEW_ITEM:
         // create the intent
         i = new Intent(this, VocalNotesItemActivity.class);

         // set the action view
         i.setAction(android.content.Intent.ACTION_VIEW);

         // set the URI
         i.setDataAndType(uri, VocalNotes.NOTES_CONTENT_TYPE_ITEM);

         // start the activity
         startActivity(i);

         // return true to indicate that the action has been picked
         return true;

         // case of editing the selected note
      case EDIT_ITEM:
         // create the intent
         i = new Intent(this, VocalNotesItemActivity.class);

         // set the action view
         i.setAction(android.content.Intent.ACTION_EDIT);

         // set the URI
         i.setDataAndType(uri, VocalNotes.NOTES_CONTENT_TYPE_ITEM);

         // start the activity
         startActivity(i);

         // return true to indicate that this action has been picked
         return true;

         // case of deleting the selected note
      case DELETE_ITEM:
         // delete the selected id from database
         getContentResolver().delete(uri, null, null);

         // notify the user
         Toast.makeText(this, R.string.deleted_note, Toast.LENGTH_SHORT).show();

         // return true to indicate that this action has been picked
         return true;

         // case of sending the selected note
      case SEND_ITEM:
         // create the intent for sending this vocal note
         i = new Intent(Intent.ACTION_SEND);

         // set the MIME type of the data to be send
         i.setType("application/json");

         try {
            // create the JSON file with this vocal notes URI
            VocalNotes.createJsonFile(getContentResolver(), uri);

            // put the JSON file URI to be send
            i.putExtra(Intent.EXTRA_STREAM,
                  VocalNotes.getJsonURI(getContentResolver()));

         } catch (FileNotFoundException f) {
            // thrown if URI is wrong or cannot write into the JSON file
            // also if the URI corresponds to many items
            f.printStackTrace();

         }

         // create the chooser from this intent to let the user choose
         // its preferred method to share this vocal note
         i = Intent.createChooser(i,
               getResources().getText(R.string.send_action));

         // start the activity for sending
         startActivity(i);

         // return true to indicate that this action has been picked
         return true;

         // default case
      default:
         // unknown actions are picked by the super class
         return super.onContextItemSelected(item);
      }
   }

   /**
    * This method is responsible of creating the menu and dynamically changing
    * the second menu option from "Order by rating" to "Order by date" and
    * vice-versa. It is automatically called before each time the menu button is
    * pressed and it shows the menu on the display. It is used instead of the
    * onCreateOptionsMenu(Menu menu) method because it allows to dynamically
    * change menu options.
    * 
    * @param menu
    *           The Menu object.
    */
   @Override
   public boolean onPrepareOptionsMenu(Menu menu) {

      // if the menu has been already created, it has to be clean to avoid the
      // duplication of its entries.
      if (menu != null) {
         menu.clear();
      }

      // get an instance of a menu inflater
      MenuInflater inflater = getMenuInflater();

      // inflate the menu options
      inflater.inflate(R.menu.main_menu, menu);

      // get the menu item regarding the ordering of the notes
      MenuItem order = menu.findItem(R.id.order);

      // get the menu item regarding the filtering of the notes
      MenuItem filter = menu.findItem(R.id.filter);

      // check if there are notes or not
      if (!dateOrderedCursor.moveToFirst()) {
         // if there aren't notes in the database then only the new note option
         // is visible.

         // set order menu item not visible
         order.setVisible(false);

         // set filter menu item not visible
         filter.setVisible(false);

      } else {
         // else (there are notes), check the ordering state
         if (!orderingState) {
            // if false then the menu option must be change to "order by date"

            // set title to order menu item
            order.setTitle(R.string.main_option2_date);

            // set filter menu item visible
            filter.setVisible(true);

         } else {
            // else (true) the menu option must be change to "order by rating"

            // set title to order menu item
            order.setTitle(R.string.main_option2);

            // set filter menu item not visible
            filter.setVisible(false);
         }
      }

      // return the super onPrepareOptionsMenu() execution
      return super.onPrepareOptionsMenu(menu);
   }

   /**
    * This method is called when an option from the menu is pressed and it is
    * responsible of executing an action in response.
    * 
    * @param item
    *           The MenuItem selected object.
    */
   @Override
   public boolean onOptionsItemSelected(MenuItem item) {
      switch (item.getItemId()) {
      // case of creating a new note
      case R.id.create_newnote:
         // construct the intent to pass at the activity
         Intent i = new Intent(this, VocalNotesItemActivity.class);

         // set the action type (insert)
         i.setAction(android.content.Intent.ACTION_INSERT);

         // set the data URI and its MIME type
         i.setDataAndType(VocalNotes.CONTENT_URI_NOTES,
               VocalNotes.NOTES_CONTENT_TYPE);

         // start the activity for creating the new note
         startActivity(i);

         // return true to indicate that the action is executed
         return true;

         // case of changing the ordering state
      case R.id.order:
         // update the current adapter
         if (orderingState) {
            // set no filter
            ratingStarsFilter = -1;

            // get the cursor for the vocal notes ordered by rate
            rateOrderedCursor = managedQuery(VocalNotes.CONTENT_URI_NOTES,
                  VocalNotes.VocalNotesContentProviderAPI.PROJECTION, null,
                  null, VocalNotes.VocalNotesContentProviderAPI.ORDER_BY_RATING);

            // set the adapter for rating ordered vocal notes
            rateAdapter = new SimpleCursorAdapter(this,
                  R.layout.main_list_item, rateOrderedCursor,
                  VocalNotes.VocalNotesContentProviderAPI.SIMPLE_PROJECTION,
                  new int[] { R.id.note_title, R.id.date_format,
                        R.id.note_rating });

            // construct a binder for rating and date
            ListBinder binder = new ListBinder();

            // set the binder to the adapter
            rateAdapter.setViewBinder(binder);

            // set the current adapter
            currentAdapter = rateAdapter;
         } else {
            currentAdapter = dateAdapter;
         }

         // invert the ordering state
         orderingState = !orderingState;

         // get the list used to show notes in the database
         ListView notes = (ListView) findViewById(R.id.notelist);

         // set the current adapter
         notes.setAdapter(currentAdapter);

         // return true to indicate that the action is executed
         return true;

         // case of filtering
      case R.id.filter:
         // create the builder of the alert dialog
         AlertDialog.Builder builder = new AlertDialog.Builder(this);

         // construct the rating bar
         final RatingBar ratingBar = new RatingBar(this);

         // set the number of visible stars
         ratingBar.setNumStars(5);

         // set the step value from a star to the next one
         ratingBar.setStepSize(1);

         // set the rating bar into the alert dialog
         builder.setView(ratingBar);

         // set the message in the alert dialog
         builder.setMessage(R.string.rating_filter);

         // set not cancelable
         builder.setCancelable(true);

         // the context to pass to the listener for creating the adapter
         // it must be final to be referred by the inner class
         final Context context = this;

         // set action for the positive button
         builder.setPositiveButton(R.string.filter,
               new DialogInterface.OnClickListener() {

                  @Override
                  public void onClick(DialogInterface dialog, int id) {
                     // read the filter value and show only the notes which
                     // rating value is the same.
                     ratingStarsFilter = (int) ratingBar.getRating();

                     // construct the selection
                     String selection = VocalNotes.VocalNotesContentProviderAPI.COLUMN_NAME_RATING
                           + " = " + ratingStarsFilter;

                     // get the cursor for the vocal notes ordered by rate
                     rateOrderedCursor = managedQuery(
                           VocalNotes.CONTENT_URI_NOTES,
                           VocalNotes.VocalNotesContentProviderAPI.PROJECTION,
                           selection,
                           null,
                           VocalNotes.VocalNotesContentProviderAPI.ORDER_BY_RATING);

                     // set the adapter for rating ordered vocal notes
                     rateAdapter = new SimpleCursorAdapter(
                           context,
                           R.layout.main_list_item,
                           rateOrderedCursor,
                           VocalNotes.VocalNotesContentProviderAPI.SIMPLE_PROJECTION,
                           new int[] { R.id.note_title, R.id.date_format,
                                 R.id.note_rating });

                     // construct a binder for rating and date
                     ListBinder binder = new ListBinder();
                     // set the binder to the adapter
                     rateAdapter.setViewBinder(binder);

                     // set the rating adapter as the current one
                     currentAdapter = rateAdapter;

                     // get the list used to show notes in the database
                     ListView notes = (ListView) findViewById(R.id.notelist);

                     // set the current adapter
                     notes.setAdapter(currentAdapter);
                  }
               });

         // set the action for the neutral button, only if any filter is already
         // applied
         if (ratingStarsFilter > -1) {
            builder.setNeutralButton(R.string.reset,
                  new DialogInterface.OnClickListener() {

                     @Override
                     public void onClick(DialogInterface dialog, int which) {
                        // read the filter value and show only the notes which
                        // rating value is the same.
                        ratingStarsFilter = -1;

                        // get the cursor for the vocal notes ordered by rate
                        rateOrderedCursor = managedQuery(
                              VocalNotes.CONTENT_URI_NOTES,
                              VocalNotes.VocalNotesContentProviderAPI.PROJECTION,
                              null,
                              null,
                              VocalNotes.VocalNotesContentProviderAPI.ORDER_BY_RATING);

                        // set the adapter for rating ordered vocal notes
                        rateAdapter = new SimpleCursorAdapter(
                              context,
                              R.layout.main_list_item,
                              rateOrderedCursor,
                              VocalNotes.VocalNotesContentProviderAPI.SIMPLE_PROJECTION,
                              new int[] { R.id.note_title, R.id.date_format,
                                    R.id.note_rating });

                        // construct a binder for rating and date
                        ListBinder binder = new ListBinder();

                        // set the binder to the adapter
                        rateAdapter.setViewBinder(binder);

                        // set the rating adapter as the current one
                        currentAdapter = rateAdapter;

                        // get the list used to show notes in the database
                        ListView notes = (ListView) findViewById(R.id.notelist);

                        // set the current adapter
                        notes.setAdapter(currentAdapter);
                     }
                  });
         }

         // set action for the negative button
         builder.setNegativeButton(R.string.cancel,
               new DialogInterface.OnClickListener() {

                  @Override
                  public void onClick(DialogInterface dialog, int id) {
                     // return to the window (deleting the alert dialog)
                     dialog.cancel();
                  }
               });

         // create the alert dialog
         AlertDialog alert = builder.create();

         // show the alert dialog
         alert.show();

         // return true to indicate that the action is executed
         return true;

         // default case
      default:
         // return false to indicate that the action isn't executed
         return false;
      }
   }

   /**
    * This class provides a binder from data to views. It is used for correctly
    * viewing the date in the locale date and time and the rate in a rating bar.
    * 
    * @author Daniela Cabiddu, Maurizio Kovacic, Giorgio Marcias
    */
   private class ListBinder implements SimpleCursorAdapter.ViewBinder {

      /**
       * This method binds: - an integer value with a rating bar - a date value
       * (in milliseconds, long type), with two different text views : the first
       * shows the date and the second one shows the clock time. - a string
       * value (the title of the note) with the scrolling text view
       * 
       * @param view
       *           The view that the adapter must manage.
       * @param cursor
       *           The cursor from which the adapter reads the information.
       * @param columnIndex
       *           The column of the cursor that contains the information.
       * 
       */
      @Override
      public boolean setViewValue(View view, Cursor cursor, int columnIndex) {

         if (view instanceof RatingBar) {
            // bind an integer value with a rating bar.

            // get the rating bar
            RatingBar rb = (RatingBar) view;

            // get the rating value from the cursor
            int n_stars = cursor.getInt(columnIndex);

            // set the rating value to the rating bar
            rb.setRating((float) n_stars);

            return true;
         } else if (view instanceof LinearLayout) {
            // bind a time value (milliseconds) with two text views
            // (one shows the date and the other shows the time)

            // get the date value from the cursor (in milliseconds)
            long date_ms = cursor.getLong(columnIndex);

            // get the string representing the last modify date (format: MMM dd,
            // yyyy)
            CharSequence date = android.text.format.DateFormat.format(
                  "MMM dd, yyyy", date_ms);

            // get the string representing the last modify time (format: h:mmaa)
            CharSequence time = android.text.format.DateFormat.format("h:mmaa",
                  date_ms);

            // get the text view where the date will be shown
            TextView date_tv = (TextView) view.findViewById(R.id.note_date);

            // get the text view where the time will be shown
            TextView time_tv = (TextView) view.findViewById(R.id.note_time);

            // set the date string to the text view
            date_tv.setText(date);

            // set the time string to the text view
            time_tv.setText(time);

            return true;
         } else if (view instanceof ScrollingTextView) {
            // bind a string (title of the note) to a scrolling text view

            // get the title of the note
            String title = cursor.getString(columnIndex);

            // set the string to the scrolling text view
            ((ScrollingTextView) view).setText(title);
         }
         return false;
      }
   }
}
